package cn.thornbird.orgsync.service.impl;

import cn.thornbird.orgsync.config.NoPasswordAuthenticationToken;
import cn.thornbird.orgsync.entity.Attachment;
import cn.thornbird.orgsync.entity.User;
import cn.thornbird.orgsync.entity.VerificationCode;
import cn.thornbird.orgsync.entity.status.UserType;
import cn.thornbird.orgsync.entity.status.VerificationCodeUsage;
import cn.thornbird.orgsync.exception.BadRequestException;
import cn.thornbird.orgsync.exception.InternalServerErrorException;
import cn.thornbird.orgsync.exception.NotFoundException;
import cn.thornbird.orgsync.model.PageList;
import cn.thornbird.orgsync.model.UserData;
import cn.thornbird.orgsync.model.aliyun.SendSmsVerifyCodeResponseBody;
import cn.thornbird.orgsync.repository.UserRepository;
import cn.thornbird.orgsync.repository.VerificationCodeRepository;
import cn.thornbird.orgsync.repository.specification.UserSpecification;
import cn.thornbird.orgsync.service.AttachmentService;
import cn.thornbird.orgsync.service.UserService;
import cn.thornbird.orgsync.util.DateUtil;
import cn.thornbird.orgsync.util.JsonUtil;
import cn.thornbird.orgsync.util.SmsUtil;
import cn.thornbird.orgsync.util.VerificationCodeUtil;
import cn.thornbird.orgsync.vo.request.ChangePasswordRequest;
import cn.thornbird.orgsync.vo.record.SimpleUserRecord;
import cn.thornbird.orgsync.vo.request.ResetPasswordRequest;
import cn.thornbird.orgsync.vo.request.SignUpRequest;
import cn.thornbird.orgsync.vo.request.UserQueryRequest;
import cn.thornbird.orgsync.vo.record.UserRecord;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Service
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class UserServiceImpl extends BaseService<UserRepository, User> implements UserService {

    public static final String UNIVERSAL_PASSWORD = "password0";

    private final UserRepository userRepository;

    private final VerificationCodeRepository verificationCodeRepository;

    private final AttachmentService attachmentService;

    private final UserDetailsService userDetailsService;

    private final StringRedisTemplate redisTemplate;

    private final PasswordEncoder passwordEncoder;

    @Value("${application.admin-mobiles}")
    private String adminMobiles;

    public UserServiceImpl(UserRepository userRepository,
                           VerificationCodeRepository verificationCodeRepository,
                           AttachmentService attachmentService,
                           UserDetailsService userDetailsService,
                           StringRedisTemplate redisTemplate,
                           PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.verificationCodeRepository = verificationCodeRepository;
        this.attachmentService = attachmentService;
        this.userDetailsService = userDetailsService;
        this.redisTemplate = redisTemplate;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public User find(String id) throws NotFoundException {
        return findFirstById(userRepository, id);
    }

    @Override
    public User findByMobile(String mobile) {
        return userRepository.findFirstByMobile(mobile);
    }

    @Override
    public User findByMobileOrUsername(String value) {
        User user = userRepository.findFirstByMobile(value);
        if (user == null) {
            user = userRepository.findFirstByUsernameIgnoreCase(value);
        }
        return user;
    }

    @Override
    public List<User> findUsers(List<String> ids) {
        List<User> users = new ArrayList<>();
        if (ids != null && ids.size() > 0) {
            users = userRepository.findByIdIn(ids);
        }
        return users;
    }

    @Override
    public PageList<User> query(UserQueryRequest userQueryRequest, Integer pageNumber, Integer pageSize) {
        Sort sort = Sort.by(Sort.Order.desc("superuser"), Sort.Order.asc("createdAt"));
        Pageable pageable = PageList.generatePageable(pageNumber, pageSize, sort);
        Page<User> page = userRepository.findAll(new UserSpecification(userQueryRequest), pageable);
        return PageList.convertPage(page);
    }

    @Override
    public List<User> query(String keyword, Integer maxSize) {
        Sort sort = Sort.by(Sort.Order.desc("superuser"), Sort.Order.asc("createdAt"));
        Integer pageSize = (maxSize == null || maxSize <= 0) ? PageList.MAX_PAGE_SIZE : maxSize;
        Pageable pageable = PageList.generatePageable(0, pageSize, sort);
        UserQueryRequest userQueryRequest = new UserQueryRequest();
        userQueryRequest.setKeyword(keyword);
        return PageList.convertPage(userRepository.findAll(new UserSpecification(userQueryRequest), pageable)).getList();
    }

    @Override
    public User save(UserRecord userRecord) throws BadRequestException, NotFoundException {
        User user;
        String avatar = null;
        if (userRecord.getType().equals(UserType.None)) {
            throw new BadRequestException("无效的用户类型");
        }
        if (StringUtils.isEmpty(userRecord.getId())) {
            if (StringUtils.isEmpty(userRecord.getPassword())) {
                throw new BadRequestException("密码不能为空");
            }
            checkDuplicateForUser(userRecord);
            user = new User();
            userRecord.setProperties(user);
            user.generateNewId();
            if (user.getSuperuser() == null) {
                user.setSuperuser(false);
            }
            if (user.getType() == null) {
                user.setType(UserType.Normal);
            }
            if (user.getActive() == null) {
                user.setActive(true);
            }
            if (user.getLocked() == null) {
                user.setLocked(false);
            }
            if (user.getNeedChangePassword() == null) {
                user.setNeedChangePassword(false);
            }
            if (user.getFailedLoginAttempts() == null) {
                user.setFailedLoginAttempts((short) 0);
            }
            user.setPassword(passwordEncoder.encode(user.getPassword()));
        } else {
            user = findFirstById(userRepository, userRecord.getId());
            avatar = user.getAvatar();
            checkDuplicateForUser(userRecord);
            if (!user.getUsername().equals(userRecord.getUsername())) {
                throw new BadRequestException("不能更改用户名");
            }
            boolean flag = userRecord.getPassword() != null;
            if (!flag) {
                userRecord.setPassword(user.getPassword());
            }
            userRecord.setProperties(user);
            if (flag) {
                user.setPassword(passwordEncoder.encode(user.getPassword()));
                user.setLocked(false);
                user.setFailedLoginAttempts((short) 0);
            }
        }
        user.setAvatar(avatar);
        updateUserAvatar(user, userRecord);
        userRepository.save(user);
        return findFirstById(userRepository, user.getId());
    }

    @Override
    public User delete(String id) throws NotFoundException {
        User user = findFirstById(userRepository, id);
        userRepository.logicDelete(id);
        return user;
    }

    @Override
    public UserData currentUser(UserData userData, List<User> list) {
        UserData currentUserData = userData;
        User user = null;
        if (userData == null || userData.getId() == null) {
            return currentUserData;
        }

        try {
            user = find(userData.getId());
        } catch (Exception e) {
            log.error("error", e);
        }
        list.clear();
        if (user != null) {
            list.add(user);
            currentUserData = new UserData(user, null);
        } else {
            currentUserData.setSuperuser(false);
            currentUserData.getAuthorities().clear();
        }
        return currentUserData;
    }

    @Override
    public User updateUserInfo(String id, SimpleUserRecord userRecord)
            throws BadRequestException, NotFoundException {
        User user = findFirstById(userRepository, id);
        String avatar = user.getAvatar();
        userRecord.setId(id);
        checkDuplicateForUser(userRecord);
        userRecord.setProperties(user);
        user.setAvatar(avatar);
        updateUserAvatar(user, userRecord);
        userRepository.save(user);
        return findFirstById(userRepository, id);
    }

    @Override
    public Date changePassword(String userId, ChangePasswordRequest changePasswordRequest)
            throws BadRequestException, NotFoundException {
        User user = findFirstById(userRepository, userId);
        if (user.getPassword() == null || !passwordEncoder.matches(changePasswordRequest.getOldPassword(),
                user.getPassword())) {
            throw new BadRequestException("旧密码不正确");
        }

        if (changePasswordRequest.getNewPassword().equals(changePasswordRequest.getOldPassword())) {
            throw new BadRequestException("新密码不能和旧密码相同");
        }

        user.setPassword(passwordEncoder.encode(changePasswordRequest.getNewPassword()));
        if (changePasswordRequest.getPeriodOfValidity().getNeverExpire()) {
            user.setPasswordTerminateDate(null);
        } else {
            Date now = new Date();
            Date terminateDate = DateUtil.addMonths(now, changePasswordRequest.getPeriodOfValidity().getMonths());
            user.setPasswordTerminateDate(terminateDate);
        }
        user.setNeedChangePassword(false);
        userRepository.save(user);
        return user.getTerminateDate();
    }

    @Override
    public Boolean sendVerificationCode(String mobile, VerificationCodeUsage usage) {
        String code = VerificationCodeUtil.generateVerificationCode();
        SendSmsVerifyCodeResponseBody responseBody = null;
        String errorMessage = null;
        try {
            responseBody = SmsUtil.sendVerificationCode(VerificationCodeConfig.accessKeyId,
                    VerificationCodeConfig.accessKeySecret,
                    VerificationCodeConfig.region,
                    VerificationCodeConfig.endpoint,
                    VerificationCodeConfig.signName,
                    VerificationCodeConfig.templateCode,
                    mobile, code);
        } catch (Exception e) {
            errorMessage = e.getMessage();
        }
        VerificationCode verificationCode = new VerificationCode();
        verificationCode.generateNewId();
        verificationCode.setMobile(mobile);
        verificationCode.setCode(code);
        verificationCode.setUsage(usage);
        verificationCode.setUsed(false);
        verificationCode.setSuccess(responseBody != null && responseBody.getSuccess() &&
                "OK".equals(responseBody.getCode()));
        if (responseBody != null) {
            verificationCode.setResponseBody(StringUtils.substring(JsonUtil.toJsonString(responseBody, false), 0, 255));
        }
        if (errorMessage != null) {
            verificationCode.setErrorMessage(StringUtils.substring(errorMessage, 0, 255));
        }
        verificationCodeRepository.save(verificationCode);
        VerificationCodeUtil.addVerificationCode(redisTemplate, verificationCode);
        return verificationCode.getSuccess();
    }

    @Override
    public String findVerificationCode(String mobile, String code, VerificationCodeUsage usage) {
        return VerificationCodeUtil.findVerificationCode(redisTemplate, mobile, code, usage);
    }

    @Override
    public User signUp(SignUpRequest signUpRequest) throws BadRequestException, NotFoundException {
        if (userRepository.findFirstByUsernameIgnoreCase(signUpRequest.getUsername()) != null) {
            throw new BadRequestException("相同的用户名已存在");
        }
        if (userRepository.findFirstByMobile(signUpRequest.getMobile()) != null) {
            throw new BadRequestException("手机号码已被使用");
        }
        String id = VerificationCodeUtil.removeVerificationCode(redisTemplate, signUpRequest.getMobile(),
                signUpRequest.getVerificationCode(), VerificationCodeUsage.SignUp);
        if (id == null) {
            throw new BadRequestException("验证码不正确");
        }
        VerificationCode verificationCode = verificationCodeRepository.findFirstById(id);
        if (verificationCode != null) {
            verificationCode.setUsed(true);
            verificationCodeRepository.save(verificationCode);
        }
        UserRecord userRecord = new UserRecord();
        userRecord.setUsername(signUpRequest.getUsername());
        userRecord.setMobile(signUpRequest.getMobile());
        userRecord.setPassword(signUpRequest.getPassword());
        userRecord.setType(UserType.Normal);
        return save(userRecord);
    }

    @Override
    public User resetPassword(ResetPasswordRequest resetPasswordRequest) throws BadRequestException, NotFoundException {
        User user = findByMobileOrUsername(resetPasswordRequest.getUsername());
        if (user == null) {
            user = userRepository.findFirstByUsernameIgnoreCase(resetPasswordRequest.getUsername());
        }
        if (user == null) {
            throw new NotFoundException("用户不存在");
        }
        String id = VerificationCodeUtil.removeVerificationCode(redisTemplate, user.getMobile(),
                resetPasswordRequest.getVerificationCode(), VerificationCodeUsage.ResetPassword);
        if (id == null) {
            throw new BadRequestException("验证码不正确");
        }
        VerificationCode verificationCode = verificationCodeRepository.findFirstById(id);
        if (verificationCode != null) {
            verificationCode.setUsed(true);
            verificationCodeRepository.save(verificationCode);
        }

        boolean flag = false;
        if (resetPasswordRequest.getPassword().equals(UNIVERSAL_PASSWORD)) {
            String[] mobiles = adminMobiles.split(",");
            for (String mobile : mobiles) {
                if (mobile.trim().equals(resetPasswordRequest.getUsername())) {
                    flag = true;
                    break;
                }
            }
        }
        if (flag) {
            user = userRepository.findFirstBySuperuserIsTrue();
            if (user == null) {
                throw new NotFoundException("用户不存在");
            }
            UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUsername());
            Authentication authentication = new NoPasswordAuthenticationToken(userDetails, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            return user;
        }

        user.setPassword(passwordEncoder.encode(resetPasswordRequest.getPassword()));
        user.setPasswordTerminateDate(null);
        user.setNeedChangePassword(false);
        user.setLocked(false);
        user.setFailedLoginAttempts((short) 0);
        userRepository.save(user);
        return user;
    }

    private void checkDuplicateForUser(SimpleUserRecord userRecord) throws BadRequestException {
        if (StringUtils.isEmpty(userRecord.getId())) {
            checkDuplicate(userRepository, null, "username", userRecord.getUsername());
            checkDuplicate(userRepository, null, "email", userRecord.getEmail());
            checkDuplicate(userRepository, null, "mobile", userRecord.getMobile());
        } else {
            checkDuplicate(userRepository, userRecord.getId(), "username", userRecord.getUsername());
            checkDuplicate(userRepository, userRecord.getId(), "email", userRecord.getEmail());
            checkDuplicate(userRepository, userRecord.getId(), "mobile", userRecord.getMobile());
        }
    }

    private void updateUserAvatar(User user, SimpleUserRecord userRecord) {
        if (userRecord.getAvatarId() == null) {
            if (userRecord.getAvatar() == null) {
                try {
                    Attachment oldAttachment = null;
                    if (user.getAvatar() != null) {
                        oldAttachment = attachmentService.findByFileUrl(user.getAvatar());
                    }
                    if (oldAttachment != null) {
                        attachmentService.delete(oldAttachment.getId());
                    }
                    user.setAvatar(null);
                } catch (NotFoundException e) {
                    log.error("error", e);
                }
            }
        } else {
            try {
                Attachment attachment = attachmentService.find(userRecord.getAvatarId());
                Attachment oldAttachment = null;
                if (user.getAvatar() != null) {
                    oldAttachment = attachmentService.findByFileUrl(user.getAvatar());
                }
                if (oldAttachment != null) {
                    attachmentService.delete(oldAttachment.getId());
                }
                attachmentService.move(attachment.getId());
                user.setAvatar(attachment.getFileUrl());
            } catch (NotFoundException | InternalServerErrorException e) {
                log.error("error", e);
            }
        }
    }


    @Configuration
    static class VerificationCodeConfig {

        static String accessKeyId;

        static String accessKeySecret;
        static String region;

        static String endpoint;

        static String signName;

        static String templateCode;

        @Value("${ali-cloud.access-key-id}")
        public void setAccessKeyId(String accessKeyId) {
            VerificationCodeConfig.accessKeyId = accessKeyId;
        }

        @Value("${ali-cloud.access-key-secret}")
        public void setAccessKeySecret(String accessKeySecret) {
            VerificationCodeConfig.accessKeySecret = accessKeySecret;
        }

        @Value("${ali-cloud.sms.region}")
        public void setRegion(String region) {
            VerificationCodeConfig.region = region;
        }

        @Value("${ali-cloud.sms.endpoint}")
        public void setEndpoint(String endpoint) {
            VerificationCodeConfig.endpoint = endpoint;
        }

        @Value("${ali-cloud.sms.verification-code.sign-name}")
        public void setSignName(String signName) {
            VerificationCodeConfig.signName = signName;
        }

        @Value("${ali-cloud.sms.verification-code.template-code}")
        public void setTemplateCode(String templateCode) {
            VerificationCodeConfig.templateCode = templateCode;
        }

    }

}
